# -*- mode: python -*-
###
#
# (c) 2000-2007 Wijnand Modderman <wijnand at tehmaze dot com>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
#   * Redistributions of source code must retain the above copyright notice,
#     this list of conditions, and the following disclaimer.
#   * Redistributions in binary form must reproduce the above copyright notice,
#     this list of conditions, and the following disclaimer in the
#     documentation and/or other materials provided with the distribution.
#   * Neither the name of the author of this software nor the name of
#     contributors to this software may be used to endorse or promote products
#     derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###

# system imports
import os
import glob

# DDB imports
from ddb.log import log
from ddb.tools import localImport
from ddb.event import event

# Twisted imports
from twisted.python.rebuild import rebuild

class LoadException(Exception):
    pass

class IModuleManager:
    """Manage all loadable modules."""

    def __init__(self):
        self.modules  = self # hack for ddb.event
        self.register = {}
        self.hooks    = {}
        log.msg('Locating loadable modules')
        self.loadDir('raw')
            

    #
    # (re/un)loading
    #

    def load(self, moduleName):
        if moduleName in self.register:
            raise Exception('Can not load module "%s": module already loaded')
            return
        newModule = localImport(moduleName)
        self.register[moduleName] = newModule
        event(self, 'loadModule', module=newModule, name=moduleName)
        self.record(moduleName)

    def loadDir(self, moduleDir):
        if os.path.isdir(moduleDir):
            for moduleName in glob.glob('%s%s*.py' % (moduleDir, os.sep)):
                if os.path.basename(moduleName)[0] == '_':
                    continue
                moduleName = moduleName.replace(os.sep, '.').rstrip('.py')
                try:
                    self.load(moduleName)
                except Exception,e:
                    log.err()
                else:
                    log.msg('Loaded module %s (%s)' % (moduleName, self.register[moduleName]))


    def reload(self, moduleName):
        if not moduleName in self.register:
            raise LoadException('Can not reload module "%s": not in register' % moduleName)
            return
        oldModule = self.register[moduleName]
        del self.register[moduleName]
        newModule = None
        if hasattr(oldModule, 'ALLOW_TWISTED_REBUILD') and oldModule.ALLOW_TWISTED_REBUILD:
            log.msg('Rebuilding module %s' % moduleName, debug=True)
            self.trebuild(moduleName)
        else:
            log.msg('Reloading module %s' % moduleName, debug=True)
            try:
                newModule = reload(oldModule)
            except Exception, e:
                self.register[moduleName] = oldModule
                raise Exception(e)
        # ok all went ok, or somewhere an exception would have been thrown
        if newModule:
            event(self, 'reloadModule', module=newModule, oldModule=oldModule, name=moduleName)
            self.register[moduleName] = newModule
            self.updateRefs(oldModule, newModule)
            del oldModule
            del newModule
        else:
            self.register[moduleName] = oldModule
            del oldModule

    def trebuild(self, moduleName):
        """This uses the twisted rebuild voodoo."""
        oldModule = self.register[moduleName]
        #try:
        newModule = rebuild(oldModule, 1)
        #except Exception, e:
        #    log.err()
        #    raise LoadException(e)
        #    return
        del oldModule                   # bye ref
        del self.register[moduleName]    # *bang*
        self.register[moduleName] = newModule

    def unload(self, moduleName):
        pass

    def updateRefs(self, oldModule, newModule):
        """Attempt to update all hooks that refer to the old module and
        replace them by the new one."""
        for type in self.hooks.keys():
            for callback in self.hooks[type].keys():
                print dir(callback)
                print "CLASS", callback.__class__
                print "SELF", callback.im_self

    #
    # scanning
    #

    def record(self, moduleName):
        print 'DEBUG: recording modules from %s' % moduleName
        if hasattr(self.register[moduleName], 'bind'):
            bindings = getattr(self.register[moduleName], 'bind')
            for binding in bindings:
                self.bind(moduleName, *binding)

    #
    # hooking
    #

    def bind(self, moduleName, type, callback, priority=50, ):
        if not self.hooks.has_key(type):
            self.hooks[type] = {}
        self.hooks[type][callback] = (priority, )

    def hook(self, e):
        log.msg('Hooking %s' % e.type, debug=True)
        if self.hooks.has_key(e.type):
            # sort dict by value (priority)
            hooks = self.hooks[e.type].items()
            hooks = [(v, k) for (k, v) in hooks]
            hooks.sort()        # sort priorities
            hooks.reverse()     # high priorities first
            hooks = [(k, v) for (v, k) in hooks]
            #print 'got %d hooks for %s' % (len(hooks), event.type)
            for hook in hooks:
                callback = hook[0]
                callback(e)
                # Is the hook-chain interrupted?
                if e.stopped:
                    return
        else:
            log.msg('No hooks for %s' % e.type, debug=True)

moduleManager = IModuleManager()

# vim:ts=4:sw=4:et:
